//
//  CMVideoDownloader.m
//  Cheer
//
//  Created by YIDAOMAC on 2024/1/12.
//

#import "CMVideoDownloader.h"
#import <MobileCoreServices/MobileCoreServices.h>

///代理
@protocol CMVideoURLSessionManagerDelegate <NSObject>

///
- (void)URLSession:(NSURLSession *)session didReceiveResponse:(NSURLResponse *)response;
- (void)URLSession:(NSURLSession *)session didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session didCompleteWithError:(NSError *)error;

@end

@interface CMVideoURLSessionManager : NSObject<NSURLSessionDataDelegate>

///
@property(nonatomic, strong) NSURLSession *URLSession;

///回调队列
@property(nonatomic, strong) NSOperationQueue *operationQueue;

///代理
@property(nonatomic, strong) NSMutableDictionary<NSNumber*, id<CMVideoURLSessionManagerDelegate>> *delegates;

@end

@implementation CMVideoURLSessionManager

+ (CMVideoURLSessionManager*)sharedManager
{
    static CMVideoURLSessionManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [CMVideoURLSessionManager new];
    });

    return manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        
        self.delegates = [NSMutableDictionary dictionary];
        self.operationQueue = [[NSOperationQueue alloc] init];
        self.operationQueue.maxConcurrentOperationCount = 1;
        
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        configuration.timeoutIntervalForRequest = 15;
        configuration.timeoutIntervalForResource = NSTimeIntervalSince1970;
        self.URLSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:self.operationQueue];
    }
    return self;
}

- (NSURLSessionTask*)downloadTaskWithURL:(NSURL*) URL delegate:(id<CMVideoURLSessionManagerDelegate>) delegate
{
    NSURLSessionDataTask *task = [self.URLSession dataTaskWithURL:URL];
    self.delegates[@(task.taskIdentifier)] = delegate;
    
    return task;
}

// MARK: - NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    NSHTTPURLResponse *httpURLResponse = (NSHTTPURLResponse*)response;
    if (httpURLResponse.statusCode == 200) {
        [self.delegates[@(dataTask.taskIdentifier)] URLSession:session didReceiveResponse:response];
        completionHandler(NSURLSessionResponseAllow);
    } else {
        completionHandler(NSURLSessionResponseCancel);
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    [self.delegates[@(dataTask.taskIdentifier)] URLSession:session didReceiveData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSNumber *key = @(task.taskIdentifier);
    [self.delegates[key] URLSession:session didCompleteWithError:error];
    [self.delegates removeObjectForKey:key];
}

@end

@interface CMVideoDownloader ()<CMVideoURLSessionManagerDelegate>

///当前下载的数据
@property (nonatomic, strong) NSMutableData *data;

///数据格式
@property (nonatomic, copy) NSString *mimeType;

///要下载的数据大小
@property (nonatomic, assign) long long expectedContentLength;

///当前请求
@property (nonatomic, strong) NSMutableArray<AVAssetResourceLoadingRequest*> *loadingRequests;

///下载任务
@property(nonatomic, strong) NSURLSessionTask *downloadTask;

///原始的scheme
@property(nonatomic, copy) NSString *originalScheme;

///是否已取消
@property(nonatomic, assign) BOOL isCancelled;

@end

@implementation CMVideoDownloader

+ (instancetype)downloaderWithURL:(NSURL *)URL
{
    return [[self alloc] initWithURL:URL];
}

- (instancetype)initWithURL:(NSURL *)URL
{
    self = [super init];
    if (self) {
        self.loadingRequests = [NSMutableArray array];
        NSURLComponents *URLComponents = [[NSURLComponents alloc] initWithURL:URL resolvingAgainstBaseURL:nil];
        self.originalScheme = URLComponents.scheme;
        URLComponents.scheme = @"myvideo";
        
        //resourceLoader 不支持保留的 http https
        AVURLAsset *asset = [AVURLAsset assetWithURL:URLComponents.URL];
        [asset.resourceLoader setDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)];
        _asset = asset;
    }
    
    return self;
}

- (void)cancel
{
    if (!self.isCancelled) {
        self.isCancelled = YES;
        [self.downloadTask cancel];
    }
}

- (void)pause
{
    if (self.downloadTask.state == NSURLSessionTaskStateRunning) {
        [self.downloadTask suspend];
    }
}

- (void)resume
{
    if (self.downloadTask.state == NSURLSessionTaskStateSuspended) {
        [self.downloadTask resume];
    }
}

// MARK: - AVAssetResourceLoaderDelegate

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
    if(self.downloadTask == nil) {
        NSURLComponents *URLComponents = [NSURLComponents componentsWithURL:loadingRequest.request.URL resolvingAgainstBaseURL:nil];
        URLComponents.scheme = self.originalScheme;
        self.downloadTask = [[CMVideoURLSessionManager sharedManager] downloadTaskWithURL:URLComponents.URL delegate:self];
        [self.downloadTask resume];
    }

    [self.loadingRequests addObject:loadingRequest];
    return YES;
}

- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
    [self.loadingRequests removeObject:loadingRequest];
}

- (void)processLoadingRequests
{
    NSMutableArray *requestsCompleted = [NSMutableArray array];
    //获取所有已完成AVAssetResourceLoadingRequest
    for (AVAssetResourceLoadingRequest *request in self.loadingRequests) {
        //判断AVAssetResourceLoadingRequest是否完成
        BOOL didRespondCompletely = [self respondWithDataForRequest:request];
        
        //结束AVAssetResourceLoadingRequest
        if (didRespondCompletely){
            [requestsCompleted addObject:request];
            [request finishLoading];
        }
    }

    //移除所有已完成AVAssetResourceLoadingRequest
    [self.loadingRequests removeObjectsInArray:requestsCompleted];
}

//给单个请求填充数据
- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
    //设置AVAssetResourceLoadingRequest的类型、支持断点下载、内容大小
    loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
    loadingRequest.contentInformationRequest.contentType = [UTType typeWithMIMEType:self.mimeType].identifier;
    loadingRequest.contentInformationRequest.contentLength = self.expectedContentLength;
    
    //AVAssetResourceLoadingRequest请求偏移量
    long long startOffset = loadingRequest.dataRequest.requestedOffset;
    if (loadingRequest.dataRequest.currentOffset != 0) {
        startOffset = loadingRequest.dataRequest.currentOffset;
    }
    
    //判断当前缓存数据量是否大于请求偏移量
    if (self.data.length < startOffset) {
        return NO;
    }
    
    //计算还未装载到缓存数据
    NSUInteger unreadBytes = self.data.length - (NSUInteger)startOffset;
    
    //判断当前请求到的数据大小
    NSUInteger numberOfBytesToRespondWidth = MIN((NSUInteger)loadingRequest.dataRequest.requestedLength, unreadBytes);
    
    //将缓存数据的指定片段装载到视频加载请求中
    [loadingRequest.dataRequest respondWithData:[self.data subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWidth)]];
    
    //计算装载完毕后的数据偏移量
    long long endOffset = startOffset + loadingRequest.dataRequest.requestedLength;
    
    //判断请求是否完成
    return self.data.length >= endOffset;
}

// MARK: - CMVideoURLSessionManagerDelegate

- (void)URLSession:(NSURLSession *)session didReceiveResponse:(NSURLResponse *)response
{
    self.data = [NSMutableData data];
    self.mimeType = response.MIMEType;
    self.expectedContentLength = response.expectedContentLength;
    [self processLoadingRequests];
}

- (void)URLSession:(NSURLSession *)session didReceiveData:(NSData *)data
{
    [self.data appendData:data];
    [self processLoadingRequests];
}

- (void)URLSession:(NSURLSession *)session didCompleteWithError:(NSError *)error
{
    for (AVAssetResourceLoadingRequest *request in self.loadingRequests) {
        if (error) {
            [request finishLoadingWithError:error];
        } else {
            [request finishLoading];
        }
    }
    [self.loadingRequests removeAllObjects];
    self.data = nil;
}

@end
